#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <sys/time.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/types.h>
#include <libxml/xmlmemory.h>
#include <libxml/parser.h>

#ifdef WIN32
#include <winsock2.h>
#include <windows.h>
#else
#include <semaphore.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <pthread.h>
#include <sys/socket.h>
#include <netinet/tcp.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#endif

#include "dicop.h"
#include <dicom/dicom.h>
#include <dicom/ringbuffer.h>

#ifndef SOL_TCP
# define SOL_TCP IPPROTO_TCP
#endif

#undef DEBUG_SOCKET
#undef DEBUG_XML

/* Private function definitions */
static struct dicop_app *_dicop_app_lookup(char *app_name);
static struct dicop_method_desc *_dicop_method_desc_lookup(struct dicop_app *app, char *method_name);
static struct dicop_signal_desc *_dicop_signal_desc_lookup(struct dicop_app *app, char *signal_name);
static void _dicop_method_desc_destroy(struct dicop_method_desc *method);
static void _dicop_method_data_destroy(struct dicop_method_data *method);
static void _dicop_signal_desc_destroy(struct dicop_signal_desc *signal);
static void _dicop_signal_data_destroy(struct dicop_signal_data *signal);
static struct dicop_message *_dicop_message_new(int conn_id, char *app_name, int type);
static void _dicop_message_destroy(struct dicop_message *message);
static int _dicop_message_send(struct dicop_message *message);
static struct dicop_message *_dicop_message_get_by_session(queue_t *queue, unsigned int session_id);

static int servfd;
static int serv_count = 0;
static queue_t *message_queue;
static queue_t *request_queue;
static queue_t *reply_queue;
static LIST_HEAD(app_list);
static int goodbye = 0;

#ifdef WIN32
/* Server Threads */
HANDLE serv_accept_thread;
HANDLE serv_msg_proc_thread;
DWORD serv_accept_thread_id;
DWORD serv_msg_proc_thread_id;
/* Client Threads */
HANDLE cli_msg_recv_thread;
HANDLE cli_msg_proc_thread;
DWORD cli_msg_recv_thread_id;
DWORD cli_msg_proc_thread_id;
#else
/* Server Threads */
pthread_t serv_accept_thread;
pthread_t serv_msg_proc_thread;
/* Client Threads */
pthread_t cli_msg_recv_thread;
pthread_t cli_msg_proc_thread;
#endif


static void
__sighandler_exit(int sig)
{
#ifdef WIN32
    printf("Gracefully exiting thread #%d\n", (int)GetCurrentThread());
    ExitThread(0);
#else
    printf("Gracefully exiting thread #%d\n", (int)pthread_self());
    pthread_exit(0);
#endif
    goodbye = 1;
}

void
dicop_init(void)
{
#ifdef WIN32
    WSADATA WsaData;
    WSAStartup(0x202, &WsaData);    //Socket 2.2
#endif

    message_queue = queue_create();
    request_queue = queue_create();
    reply_queue = queue_create();

    signal(SIGINT, __sighandler_exit);
#ifndef WIN32   //windows does not have SIGQUIT.
    signal(SIGQUIT, __sighandler_exit);
#endif
    signal(SIGTERM, __sighandler_exit);
}


void
dicop_release(void)
{
    if(message_queue) {
        queue_destroy(message_queue);
        message_queue = NULL;
    }

    if(request_queue) {
        queue_destroy(request_queue);
        request_queue = NULL;
    }

    if(reply_queue) {
        queue_destroy(reply_queue);
        reply_queue = NULL;
    }
}

struct dicop_app *
dicop_app_new(char *app_name)
{
    struct dicop_app *new_app;

    new_app = malloc(sizeof(struct dicop_app));

    if (new_app == NULL) {
        perror("malloc");
        return NULL;
    }

    new_app->app_name = strdup(app_name);
    if (new_app->app_name == NULL) {
        free(new_app);
        return NULL;
    }

    new_app->server_id = 0;
    new_app->client_count = 0;
    bzero(new_app->client_id, sizeof(int) * DICOP_MAX_CONN);

    INIT_LIST_HEAD(&new_app->meth_desc_list);
    INIT_LIST_HEAD(&new_app->sig_desc_list);

    list_add_tail(&new_app->app_node, &app_list);

    return new_app;
}

void
dicop_app_destroy(struct dicop_app *app)
{
    struct dicop_method_desc *method_desc, *mpos;
    struct dicop_signal_desc *signal_desc, *spos;

    if (app) {
        list_for_each_entry_safe(struct dicop_method_desc, method_desc, mpos,
                                 &app->meth_desc_list, desc_node) {
            list_del(&method_desc->desc_node);
            _dicop_method_desc_destroy(method_desc);
        }

        list_for_each_entry_safe(struct dicop_signal_desc, signal_desc, spos,
                                 &app->sig_desc_list, desc_node) {
            list_del(&signal_desc->desc_node);
            _dicop_signal_desc_destroy(signal_desc);
        }

        list_del(&app->app_node);

        if (app->app_name)
            free(app->app_name);

        free(app);
    }
}

struct dicop_app *
dicop_app_query(int server_id, char *app_name)
{
    struct dicop_message *request_msg, *reply_msg;
    struct dicop_app *app;

    request_msg = _dicop_message_new(server_id, app_name, DICOP_MESSAGE_TYPE_DEF_REQ);
    if (request_msg == NULL)
        return NULL;

    queue_push(request_queue, (void *)request_msg);

    if (_dicop_message_send(request_msg) > 0) {
        /* Wait DefReply notify from _dicop_message_handler thread */
        PRINT_INFO("Waiting for DefReply with session=%u\n", request_msg->session_id);

#ifdef WIN32
        WaitForSingleObject(request_msg->session_sem, INFINITE);
#else
        if (sem_wait(request_msg->session_sem) < 0) {
            PRINT_ERROR("Failed while waiting session semaphore for request message\n");
            return NULL;
        }
#endif

        PRINT_INFO("DefReply acked with session=%u\n", request_msg->session_id);
        reply_msg = _dicop_message_get_by_session(reply_queue, request_msg->session_id);
        if (reply_msg == NULL) {
            PRINT_ERROR("No session message found, but signaled!\n");
            return NULL;
        }

        if (reply_msg->error < 0) {
            dicop_error_print(reply_msg->error);
            _dicop_message_destroy(request_msg);
            return NULL;
        }

        _dicop_message_destroy(request_msg);

        app = _dicop_app_lookup(reply_msg->app_name);
        if (app) {
            PRINT_INFO("App [%s] is already exists.\n", reply_msg->app_name);
            return app;
        }

        app = dicop_app_new(reply_msg->app_name);
        if (app) {
            app->server_id = reply_msg->conn_id;
            /* Move method/signal description list from reply_msg to app */
            list_splice_init(&reply_msg->meth_desc_list, &app->meth_desc_list);
            list_splice_init(&reply_msg->sig_desc_list, &app->sig_desc_list);

            PRINT_INFO("New App [%s] created!\n", reply_msg->app_name);

            _dicop_message_destroy(reply_msg);

            return app;
        }
        else {
            PRINT_ERROR("Unable to create new App [%s]\n", reply_msg->app_name);
        }
    }
    else {
        PRINT_ERROR("Unable to send DefRequest message\n");
        /* Remove pushed request message from request_queue */
        _dicop_message_get_by_session(request_queue, request_msg->session_id);
        _dicop_message_destroy(request_msg);
    }

    return NULL;
}

static struct dicop_app *
_dicop_app_lookup(char *app_name)
{
    struct dicop_app *app;
    struct list_head *app_list_pos;
    struct list_head *app_safe;

    if (app_name) {
        list_for_each_safe(app_list_pos, app_safe, &app_list) {
            app = list_entry(app_list_pos, struct dicop_app, app_node);
            if (strncmp(app_name, app->app_name, strlen(app->app_name)) == 0)
                return app;
        }
    }
    return NULL;
}

struct dicop_method_desc *
dicop_method_new(struct dicop_app *app, char *method_name)
{
    struct dicop_method_desc *method_desc;

    if (app == NULL || method_name == NULL)
        return NULL;

    method_desc = calloc(1, sizeof(struct dicop_method_desc));
    if (!method_desc) {
        return NULL;
    }

    method_desc->name = strdup(method_name);
    if (!method_desc->name) {
        free(method_desc);
        return NULL;
    }

    INIT_LIST_HEAD(&method_desc->arg_list);
    INIT_LIST_HEAD(&method_desc->rep_list);

    list_add_tail(&method_desc->desc_node, &app->meth_desc_list);

    method_desc->app = app;

    return method_desc;
}

void
dicop_method_callback_install(struct dicop_method_desc *desc,
                              callback_t (*callback)(callback_args_t))
{
    if (!desc) return;
    desc->callback = callback;
}

int
dicop_method_args_init(struct dicop_method_desc *desc, unsigned int arg_nbr, ...)
{
    va_list ap;
    unsigned int arg_idx;
    struct dicop_args *arg;
    char *str_name;

    if (desc == NULL)
        return DICOP_ERROR_ARGUMENTS;

    if (arg_nbr == 0)
        return DICOP_OK;

    desc->arg_nbr = arg_nbr;
    INIT_LIST_HEAD(&desc->arg_list);

    va_start(ap, arg_nbr);

    for (arg_idx = 0; arg_idx < arg_nbr; arg_idx++) {
        arg = malloc(sizeof(struct dicop_args));
        if (arg == NULL)
            return DICOP_ERROR_MEMORY;

        str_name = va_arg(ap, char *);
        arg->name = strdup(str_name);
        if (arg->name == NULL) {
            free(arg);
            return DICOP_ERROR_MEMORY;
        }

        arg->value = NULL;
        list_add_tail(&arg->arg_node, &desc->arg_list);
    }

    va_end(ap);

    return DICOP_OK;
}

int
dicop_method_reply_init(struct dicop_method_desc *desc, unsigned int rep_nbr, ...)
{
    va_list ap;
    unsigned int rep_idx;
    struct dicop_args *rep;
    char *str_name;

    if (desc == NULL)
        return DICOP_ERROR_ARGUMENTS;

    if (rep_nbr == 0)
        return DICOP_OK;

    desc->rep_nbr = rep_nbr;
    INIT_LIST_HEAD(&desc->rep_list);

    va_start(ap, rep_nbr);

    for (rep_idx = 0; rep_idx < rep_nbr; rep_idx++) {
        rep = malloc(sizeof(struct dicop_args));
        if (rep == NULL)
            return DICOP_ERROR_MEMORY;

        str_name = va_arg(ap, char *);
        rep->name = strdup(str_name);
        if (rep->name == NULL) {
            free(rep);
            return DICOP_ERROR_MEMORY;
        }
        rep->value = NULL;
        list_add_tail(&rep->arg_node, &desc->rep_list);
    }

    va_end(ap);

    return DICOP_OK;
}

static struct dicop_method_desc *
_dicop_method_desc_lookup(struct dicop_app *app, char *method_name)
{
    struct list_head *node;
    struct list_head *desc_safe;
    struct dicop_method_desc *desc;

    if (app == NULL || method_name == NULL)
        return NULL;

    list_for_each_safe(node, desc_safe, &app->meth_desc_list) {
        desc = list_entry(node, struct dicop_method_desc, desc_node);
        if (desc) {
            if (strncmp(method_name, desc->name, strlen(desc->name)) == 0)
                return desc;
        }
    }

    return NULL;
}

int
dicop_method_call_real(int wait, struct dicop_app *app, char *method_name, ...)
{
    va_list ap;
    int arg_idx;
    int result = DICOP_OK;
    struct dicop_method_desc *method_desc;
    struct dicop_method_data *method_data;
    struct dicop_message *method_call_msg;
    struct dicop_args *arg;
    char *str_name;
    char *str_value;

    method_desc = _dicop_method_desc_lookup(app, method_name);

    if (method_desc == NULL)
        return DICOP_ERROR_METHOD_CALL;

    method_data = malloc(sizeof(struct dicop_method_data));
    if (method_data == NULL)
        return DICOP_ERROR_MEMORY;

    method_data->name = strdup(method_name);
    if (method_data->name == NULL) {
        free(method_data);
        return DICOP_ERROR_MEMORY;
    }
    method_data->type = DICOP_METHOD_TYPE_CALL;
    INIT_LIST_HEAD(&method_data->arg_list);
    INIT_LIST_HEAD(&method_data->data_node);

    method_call_msg = _dicop_message_new(app->server_id, app->app_name,
                                         DICOP_MESSAGE_TYPE_CONTEXT);
    if (method_call_msg == NULL) {
        _dicop_method_data_destroy(method_data);
        return DICOP_ERROR_MEMORY;
    }

    va_start(ap, method_name);

    for (arg_idx = 0; arg_idx < method_desc->arg_nbr; arg_idx++) {
        arg = malloc(sizeof(struct dicop_args));
        if (arg == NULL) {
            _dicop_method_data_destroy(method_data);
            _dicop_message_destroy(method_call_msg);
            return DICOP_ERROR_MEMORY;
        }

        str_name = va_arg(ap, char *);
        arg->name = strdup(str_name);
        if (arg->name == NULL) {
            free(arg);
            _dicop_method_data_destroy(method_data);
            _dicop_message_destroy(method_call_msg);
            return DICOP_ERROR_MEMORY;
        }

        str_value = va_arg(ap, char *);
        arg->value = strdup(str_value);
        if (arg->value == NULL) {
            free(arg->name);
            free(arg);
            _dicop_method_data_destroy(method_data);
            _dicop_message_destroy(method_call_msg);
            return DICOP_ERROR_MEMORY;
        }

        list_add_tail(&arg->arg_node, &method_data->arg_list);
    }

    va_end(ap);

    list_add_tail(&method_data->data_node, &method_call_msg->meth_data_list);

    if (wait) {
        queue_push(request_queue, (void *)method_call_msg);

        if (_dicop_message_send(method_call_msg) > 0) {
            /* Wait method reply from _dicop_message_handler thread */
#ifdef WIN32
            WaitForSingleObject(method_call_msg->session_sem, INFINITE);
#else
            if (sem_wait(method_call_msg->session_sem) < 0) {
                PRINT_ERROR("Failed while waiting session semaphore for method call message\n");
            }
#endif
            result = method_call_msg->session_id;
        }
        else {
            PRINT_ERROR("Unable to send method call message.\n");
            _dicop_message_get_by_session(request_queue, method_call_msg->session_id);
        }
    }
    else {
        if (_dicop_message_send(method_call_msg) <= 0) {
            PRINT_ERROR("Unable to send method call message.\n");
        }
    }

    _dicop_message_destroy(method_call_msg);

    return result;
}

int
dicop_method_call_get_reply(struct dicop_app *app, char *method_name,
                            unsigned int session_id, ...)
{
    va_list ap;
    struct dicop_method_desc *method_desc;
    struct dicop_method_data *method_data;
    struct dicop_message *reply_msg;
    struct dicop_args *reply;
    struct list_head *meth_pos, *rep_pos;
    struct list_head *data_safe, *arg_safe;
    char *rep_name, *rep_value;
    int rep_count = 0;

    method_desc = _dicop_method_desc_lookup(app, method_name);

    if (method_desc == NULL)
        return DICOP_ERROR_METHOD_CALL;

    reply_msg = _dicop_message_get_by_session(reply_queue, session_id);
    if (reply_msg == NULL) {
        PRINT_ERROR("No message returns to session queue, but signaled!\n");
        return DICOP_ERROR_PROTOCOL;
    }

    va_start(ap, session_id);

    list_for_each_safe(meth_pos, data_safe, &reply_msg->meth_data_list) {
        method_data = list_entry(meth_pos, struct dicop_method_data, data_node);

        if (!strncmp(method_data->name, method_name, strlen(method_data->name)) &&
            method_data->type == DICOP_METHOD_TYPE_REPLY) {

            list_for_each_safe(rep_pos, arg_safe, &method_data->arg_list) {
                reply = list_entry(rep_pos, struct dicop_args, arg_node);

                if (reply) {
                    if (++rep_count > method_desc->rep_nbr) {
                        PRINT_WARNING("Number of reply arguments is different "
                                      "from method description, should be %d\n",
                                      method_desc->rep_nbr);
                        break;
                    }

                    rep_name = va_arg(ap, char *);
                    rep_value = va_arg(ap, char *);

                    if (strncmp(rep_name, reply->name, strlen(reply->name)) == 0)
                        strncpy(rep_value, reply->value, strlen(reply->value));
                }
            }

            break;
        }
    }

    va_end(ap);

    _dicop_message_destroy(reply_msg);

    if (rep_count == method_desc->rep_nbr)
        return DICOP_OK;

    return DICOP_ERROR_METHOD_REPLY;
}

static void
_dicop_method_desc_destroy(struct dicop_method_desc *method)
{
    struct dicop_args *arg, *pos;

    if (method) {
        list_for_each_entry_safe(struct dicop_args, arg, pos,
                                 &method->arg_list, arg_node) {
            list_del(&arg->arg_node);
            if (arg->name) free(arg->name);
            free(arg);
        }

        list_for_each_entry_safe(struct dicop_args, arg, pos,
                                 &method->rep_list, arg_node) {
            list_del(&arg->arg_node);
            if (arg->name) free(arg->name);
            free(arg);
        }

        if (method->name)
            free(method->name);

        free(method);
    }
}

static void
_dicop_method_data_destroy(struct dicop_method_data *method)
{
    struct dicop_args *arg, *pos;

    if (method) {
        list_for_each_entry_safe(struct dicop_args, arg, pos,
                                 &method->arg_list, arg_node) {
            list_del(&arg->arg_node);

            if (arg->name)
                free(arg->name);

            if (arg->value)
                free(arg->value);

            free(arg);
        }

        if (method->name)
            free(method->name);

        free(method);
    }
}

struct dicop_signal_desc *
dicop_signal_new(struct dicop_app *app, char *signal_name)
{
    struct dicop_signal_desc *signal_desc;

    if (app == NULL || signal_name == NULL)
        return NULL;

    signal_desc = calloc(1, sizeof(struct dicop_signal_desc));
    if (signal_desc == NULL) {
        perror("calloc");
        return NULL;
    }

    signal_desc->name = strdup(signal_name);
    if (signal_desc->name == NULL) {
        free(signal_desc);
        perror("calloc");
        return NULL;
    }

    INIT_LIST_HEAD(&signal_desc->arg_list);
    list_add_tail(&signal_desc->desc_node, &app->sig_desc_list);

    signal_desc->app = app;

    return signal_desc;
}

int
dicop_signal_args_init(struct dicop_signal_desc *desc, unsigned int arg_nbr, ...)
{
    va_list ap;
    unsigned int arg_idx;
    struct dicop_args *arg;
    char *str_name;

    if (desc == NULL)
        return DICOP_ERROR_ARGUMENTS;

    if (arg_nbr == 0)
        return DICOP_OK;

    desc->arg_nbr = arg_nbr;
    INIT_LIST_HEAD(&desc->arg_list);

    va_start(ap, arg_nbr);

    for (arg_idx = 0; arg_idx < arg_nbr; arg_idx++) {
        arg = malloc(sizeof(struct dicop_args));
        if (arg == NULL)
            return DICOP_ERROR_MEMORY;

        str_name = va_arg(ap, char *);
        arg->name = strdup(str_name);
        if (arg->name == NULL) {
            free(arg);
            return DICOP_ERROR_MEMORY;
        }
        arg->value = NULL;
        list_add_tail(&arg->arg_node, &desc->arg_list);
    }

    va_end(ap);

    return DICOP_OK;
}

int
dicop_signal_connect(struct dicop_app *app, char *signal_name,
                     callback_t (*callback)(callback_args_t))
{
    struct dicop_signal_desc *signal_desc;

    signal_desc = _dicop_signal_desc_lookup(app, signal_name);
    if (signal_desc == NULL)
        return DICOP_ERROR_SIGNAL;

    signal_desc->callback = callback;

    return DICOP_OK;
}

int
dicop_signal_emit(struct dicop_signal_desc *signal_desc, ...)
{
    va_list ap;
    int arg_idx, cli_idx, cli_count;
    /*struct dicop_signal_desc *signal_desc;*/
    struct dicop_signal_data *signal_data;
    struct dicop_message *signal_msg;
    struct dicop_args *arg;
    char *str_name, *str_value;

/*
    signal_desc = _dicop_signal_desc_lookup(app, signal_name);

    if (!signal_desc)
        return DICOP_ERROR_SIGNAL;
*/
    if (serv_count == 0)
        return DICOP_OK;

    signal_data = malloc(sizeof(struct dicop_signal_data));
    if (signal_data == NULL)
        return DICOP_ERROR_MEMORY;

    signal_data->name = strdup(signal_desc->name);
    if (signal_data->name == NULL) {
        free(signal_data);
        return DICOP_ERROR_MEMORY;
    }

    INIT_LIST_HEAD(&signal_data->arg_list);
    INIT_LIST_HEAD(&signal_data->data_node);

    signal_msg = _dicop_message_new(0, signal_desc->app->app_name,
                                    DICOP_MESSAGE_TYPE_CONTEXT);
    if (signal_msg == NULL) {
        _dicop_signal_data_destroy(signal_data);
        return DICOP_ERROR_MEMORY;
    }

    va_start(ap, signal_desc);

    for (arg_idx = 0; arg_idx < signal_desc->arg_nbr; arg_idx++) {
        arg = malloc(sizeof(struct dicop_args));
        if (arg == NULL) {
            _dicop_message_destroy(signal_msg);
            _dicop_signal_data_destroy(signal_data);
            return DICOP_ERROR_MEMORY;
        }

        str_name = va_arg(ap, char *);
        arg->name = strdup(str_name);
        if (arg->name == NULL) {
            free(arg);
            _dicop_message_destroy(signal_msg);
            _dicop_signal_data_destroy(signal_data);
            return DICOP_ERROR_MEMORY;
        }

        str_value = va_arg(ap, char *);
        arg->value = strdup(str_value);
        if (arg->value == NULL) {
            free(arg->name);
            free(arg);
            _dicop_message_destroy(signal_msg);
            _dicop_signal_data_destroy(signal_data);
            return DICOP_ERROR_MEMORY;
        }

        list_add_tail(&arg->arg_node, &signal_data->arg_list);
    }

    va_end(ap);

    list_add_tail(&signal_data->data_node, &signal_msg->sig_data_list);

    /* Broadcast signal to all clients */
    for (cli_idx = 0, cli_count = 0;
         cli_count < signal_desc->app->client_count;
         ++cli_idx) {
        if (signal_desc->app->client_id[cli_idx] == 0)
            continue;
        signal_msg->conn_id = signal_desc->app->client_id[cli_idx];
        if (_dicop_message_send(signal_msg) <= 0) {
            PRINT_ERROR("Unable to send signal to client [ID=%d].\n",
                        signal_desc->app->client_id[cli_idx]);
        }
        ++cli_count;
    }

    _dicop_message_destroy(signal_msg);

    return DICOP_OK;
}

static struct dicop_signal_desc *
_dicop_signal_desc_lookup(struct dicop_app *app, char *signal_name)
{
    struct list_head *node;
    struct list_head *desc_safe;
    struct dicop_signal_desc *desc;

    if (app == NULL || signal_name == NULL)
        return NULL;

    list_for_each_safe(node, desc_safe, &app->sig_desc_list) {
        desc = list_entry(node, struct dicop_signal_desc, desc_node);
        if (desc) {
            if (strncmp(signal_name, desc->name, strlen(desc->name)) == 0)
                return desc;
        }
    }

    return NULL;
}

static void
_dicop_signal_desc_destroy(struct dicop_signal_desc *signal)
{
    struct dicop_args *arg, *pos;

    if (signal) {
        list_for_each_entry_safe(struct dicop_args, arg, pos,
                                 &signal->arg_list, arg_node) {
            list_del(&arg->arg_node);

            if (arg->name)
                free(arg->name);

            free(arg);
        }

        if (signal->name)
            free(signal->name);

        free(signal);
    }
}

static void
_dicop_signal_data_destroy(struct dicop_signal_data *signal)
{
    struct dicop_args *arg, *pos;

    if (signal) {
        list_for_each_entry_safe(struct dicop_args, arg, pos,
                                 &signal->arg_list, arg_node) {
            list_del(&arg->arg_node);

            if (arg->name)
                free(arg->name);

            if (arg->value)
                free(arg->value);

            free(arg);
        }

        if (signal->name)
            free(signal->name);

        free(signal);
    }
}

unsigned int
_dicop_gen_session_id(void)
{
#define MILLION 1000000L
    struct timeval session_time;

    if (gettimeofday(&session_time, NULL) == -1) {
        perror("gettimeofday");
        return 0;
    }

    return session_time.tv_sec * MILLION + session_time.tv_usec;
}

static struct dicop_message *
_dicop_message_new(int conn_id, char *app_name, int type)
{
    struct dicop_message *message;

    message = malloc(sizeof(struct dicop_message));
    if (message == NULL)
        return NULL;

    message->conn_id = conn_id;
    message->session_id = _dicop_gen_session_id();
#ifdef WIN32
    message->session_sem = CreateSemaphore(NULL, 0, 1, NULL);
    if(message->session_sem == (HANDLE) NULL) {
        PRINT_ERROR("Unable to create session semaphore\n");
        free(message);
        return NULL;
    }
#else
#if 0
    if (sem_init(&message->session_sem, 0, 0) < 0) {
        PRINT_ERROR("Unable to init message semaphore\n");
        free(message);
        return NULL;
    }
#else
    if ((message->session_sem = sem_open("session_sem", O_CREAT, 0644, 0)) == SEM_FAILED) {
        PRINT_ERROR("Unable to open session semaphore\n");
        free(message);
        return NULL;
    }
#endif
#endif

    message->error = DICOP_OK;
    if (app_name) {
        message->app_name = strdup(app_name);
        if (message->app_name == NULL) {
            free(message);
            return NULL;
        }
    }
    else
        message->app_name = NULL;
    message->type = type;
    message->app = NULL;

    INIT_LIST_HEAD(&message->meth_desc_list);
    INIT_LIST_HEAD(&message->meth_data_list);
    INIT_LIST_HEAD(&message->sig_desc_list);
    INIT_LIST_HEAD(&message->sig_data_list);

    return message;
}

static int
_dicop_message_parse_defreply(xmlNodePtr cur, struct dicop_message *message)
{
    struct dicop_method_desc *method_desc, *mdesc_pos;
    struct dicop_signal_desc *signal_desc;
    struct dicop_args *new_arg;
    xmlNodePtr xml_arg;
    char *method_name;
    char *str_type, *str_name;
    int type;
    int method_exists = 0;

    if (message == NULL)
        return DICOP_ERROR_ARGUMENTS;

    while (cur) {
        if (xmlStrcmp(cur->name, BAD_CAST "method") == 0) {
            method_exists = 0;
            method_name = (char *)xmlGetProp(cur, BAD_CAST "name");

            list_for_each_entry_safe_reverse(struct dicop_method_desc,
                                             method_desc,
                                             mdesc_pos,
                                             &message->meth_desc_list,
                                             desc_node) {
                if (strcmp(method_name, method_desc->name) == 0) {
                    method_exists = 1;
                    break;
                }
            }

            if (method_exists == 0) {
                method_desc = calloc(1, sizeof(struct dicop_method_desc));
                if (method_desc == NULL) {
                    return DICOP_ERROR_MEMORY;
                }

                method_desc->name = strdup(method_name);
                if (method_desc->name == NULL) {
                    free(method_desc);
                    return DICOP_ERROR_MEMORY;
                }
                method_desc->arg_nbr = 0;
                method_desc->rep_nbr = 0;

                list_add_tail(&method_desc->desc_node, &message->meth_desc_list);
                INIT_LIST_HEAD(&method_desc->arg_list);
                INIT_LIST_HEAD(&method_desc->rep_list);
            }

            str_type = (char *)xmlGetProp(cur, BAD_CAST "type");
            if (str_type == NULL) {
                _dicop_method_desc_destroy(method_desc);
                return DICOP_ERROR_PROT_FORMAT;
            }

            if (strncmp(str_type, "call", 4) == 0)
                type = DICOP_METHOD_TYPE_CALL;
            else if (strncmp(str_type, "reply", 5) == 0)
                type = DICOP_METHOD_TYPE_REPLY;
            else {
                free(method_desc);
                return DICOP_ERROR_PROT_FORMAT;
            }

            xml_arg = cur->children;
            while (xml_arg) {
                if (xmlStrcmp(xml_arg->name, BAD_CAST "arg") == 0) {
                    new_arg = calloc(1, sizeof(struct dicop_args));
                    if (new_arg == NULL) {
                        _dicop_method_desc_destroy(method_desc);
                        return DICOP_ERROR_MEMORY;
                    }

                    str_name = (char *)xmlGetProp(xml_arg, BAD_CAST "name");
                    new_arg->name = strdup(str_name);
                    if (new_arg->name == NULL) {
                        free(new_arg);
                        _dicop_method_desc_destroy(method_desc);
                        return DICOP_ERROR_MEMORY;
                    }

                    if (type == DICOP_METHOD_TYPE_CALL) {
                        ++method_desc->arg_nbr;
                        list_add_tail(&new_arg->arg_node, &method_desc->arg_list);
                    }
                    else {
                        ++method_desc->rep_nbr;
                        list_add_tail(&new_arg->arg_node, &method_desc->rep_list);
                    }
                }

                xml_arg = xml_arg->next;
            }
        }
        else if (xmlStrcmp(cur->name, BAD_CAST "signal") == 0) {
            signal_desc = calloc(1, sizeof(struct dicop_signal_desc));
            if (signal_desc == NULL) {
                return DICOP_ERROR_MEMORY;
            }

            str_name = (char *)xmlGetProp(cur, BAD_CAST "name");
            signal_desc->name = strdup(str_name);
            if (signal_desc->name == NULL) {
                free(signal_desc);
                return DICOP_ERROR_MEMORY;
            }
            signal_desc->arg_nbr = 0;
            list_add_tail(&signal_desc->desc_node, &message->sig_desc_list);
            INIT_LIST_HEAD(&signal_desc->arg_list);

            xml_arg = cur->children;
            while (xml_arg) {
                if (xmlStrcmp(xml_arg->name, BAD_CAST "arg") == 0) {
                    new_arg = calloc(1, sizeof(struct dicop_args));
                    if (new_arg == NULL) {
                        _dicop_signal_desc_destroy(signal_desc);
                        return DICOP_ERROR_MEMORY;
                    }

                    str_name = (char *)xmlGetProp(xml_arg, BAD_CAST "name");
                    new_arg->name = strdup(str_name);
                    if (new_arg->name == NULL) {
                        free(new_arg);
                        _dicop_signal_desc_destroy(signal_desc);
                        return DICOP_ERROR_MEMORY;
                    }

                    ++signal_desc->arg_nbr;
                    list_add_tail(&new_arg->arg_node, &signal_desc->arg_list);
                }

                xml_arg = xml_arg->next;
            }
        }

        cur = cur->next;
    }

    return DICOP_OK;
}

static int
_dicop_message_parse_context(xmlNodePtr cur, struct dicop_message *message)
{
    struct dicop_method_data *method_data;
    struct dicop_signal_data *signal_data;
    struct dicop_args *new_arg;
    xmlNodePtr xml_arg;
    char *str_type, *str_name, *str_value;

    if (message == NULL)
        return DICOP_ERROR_ARGUMENTS;

    while (cur) {
        if (xmlStrcmp(cur->name, BAD_CAST "method") == 0) {
            method_data = malloc(sizeof(struct dicop_method_data));
            if (method_data == NULL) {
                perror("malloc");
                return DICOP_ERROR_MEMORY;
            }

            str_name = (char *)xmlGetProp(cur, BAD_CAST "name");
            method_data->name = strdup(str_name);
            if (method_data->name == NULL) {
                free(method_data);
                return DICOP_ERROR_MEMORY;
            }

            str_type = (char *)xmlGetProp(cur, BAD_CAST "type");
            if (str_type == NULL) {
                free(method_data);
                return DICOP_ERROR_PROT_FORMAT;
            }

            if (strncmp(str_type, "call", 4) == 0)
                method_data->type = DICOP_METHOD_TYPE_CALL;
            else if (strncmp(str_type, "reply", 5) == 0)
                method_data->type = DICOP_METHOD_TYPE_REPLY;
            else {
                free(method_data);
                return DICOP_ERROR_PROT_FORMAT;
            }

            list_add_tail(&method_data->data_node, &message->meth_data_list);
            INIT_LIST_HEAD(&method_data->arg_list);

            xml_arg = cur->children;
            while (xml_arg) {
                if (xmlStrcmp(xml_arg->name, BAD_CAST "arg") == 0) {
                    new_arg = calloc(1, sizeof(struct dicop_args));
                    if (new_arg == NULL) {
                        _dicop_method_data_destroy(method_data);
                        return DICOP_ERROR_MEMORY;
                    }

                    str_name = (char *)xmlGetProp(xml_arg, BAD_CAST "name");
                    new_arg->name = strdup(str_name);
                    if (new_arg->name == NULL) {
                        free(new_arg);
                        _dicop_method_data_destroy(method_data);
                        return DICOP_ERROR_MEMORY;
                    }

                    str_value = (char *)xmlNodeGetContent(xml_arg);
                    new_arg->value = strdup(str_value);
                    if (new_arg->value == NULL) {
                        free(new_arg->name);
                        free(new_arg);
                        _dicop_method_data_destroy(method_data);
                        return DICOP_ERROR_MEMORY;
                    }

                    list_add_tail(&new_arg->arg_node, &method_data->arg_list);
                }

                xml_arg = xml_arg->next;
            }
        }
        else if (xmlStrcmp(cur->name, BAD_CAST "signal") == 0) {
            signal_data = calloc(1, sizeof(struct dicop_signal_data));
            if (signal_data == NULL) {
                return DICOP_ERROR_MEMORY;
            }

            str_name = (char *)xmlGetProp(cur, BAD_CAST "name");
            signal_data->name = strdup(str_name);
            if (signal_data->name == NULL) {
                free(signal_data);
                return DICOP_ERROR_PROT_FORMAT;
            }

            list_add_tail(&signal_data->data_node, &message->sig_data_list);
            INIT_LIST_HEAD(&signal_data->arg_list);

            xml_arg = cur->children;
            while (xml_arg) {
                if (xmlStrcmp(xml_arg->name, BAD_CAST "arg") == 0) {
                    new_arg = calloc(1, sizeof(struct dicop_args));
                    if (new_arg == NULL) {
                        _dicop_signal_data_destroy(signal_data);
                        return DICOP_ERROR_MEMORY;
                    }

                    str_name = (char *)xmlGetProp(xml_arg, BAD_CAST "name");
                    new_arg->name = strdup(str_name);
                    if (new_arg->name == NULL) {
                        free(new_arg);
                        _dicop_signal_data_destroy(signal_data);
                        return DICOP_ERROR_MEMORY;
                    }

                    str_value = (char *)xmlNodeGetContent(xml_arg);
                    new_arg->value = strdup(str_value);
                    if (new_arg->value == NULL) {
                        free(new_arg->name);
                        free(new_arg);
                        _dicop_signal_data_destroy(signal_data);
                        return DICOP_ERROR_MEMORY;
                    }

                    list_add_tail(&new_arg->arg_node, &signal_data->arg_list);
                }

                xml_arg = xml_arg->next;
            }
        }

        cur = cur->next;
    }

    return DICOP_OK;
}

static struct dicop_message *
_dicop_message_parse(char *buffer, int length, int *err)
{
    struct dicop_message *message;
    xmlDocPtr doc;
    xmlNodePtr root, cur;
    xmlChar *str_type;

    if (!buffer || length <= 0) {
        *err = DICOP_ERROR_ARGUMENTS;
        return NULL;
    }

    doc = xmlParseMemory(buffer, length);
    if (doc == NULL) {
        PRINT_ERROR("Error while parsing message from memory\n");
        *err = DICOP_ERROR_PROT_FORMAT;
        return NULL;
    }

    root = xmlDocGetRootElement(doc);
    if (root == NULL) {
        PRINT_ERROR("Error while getting root element\n");
        xmlFreeDoc(doc);
        *err = DICOP_ERROR_PROT_ROOT;
        return NULL;
    }

    if (xmlStrcmp(root->name, BAD_CAST "dicop")) {
        PRINT_ERROR("Unrecognized message format [dicop]\n");
        xmlFreeDoc(doc);
        *err = DICOP_ERROR_PROT_ROOT;
        return NULL;
    }

    message = _dicop_message_new(0, NULL, DICOP_MESSAGE_TYPE_INITIAL);
    if (message == NULL) {
        perror("_dicop_message_new");
        xmlFreeDoc(doc);
        *err = DICOP_ERROR_MEMORY;
        return NULL;
    }

    cur = root->children;
    while (cur) {
        if (xmlStrcmp(cur->name, BAD_CAST "session") == 0) {
            message->session_id = atoi((char *)xmlNodeGetContent(cur));
            break;
        }
        cur = cur->next;
    }

    if (message->session_id == 0) {
        PRINT_ERROR("Bad session ID.\n");
        free(message);
        xmlFreeDoc(doc);
        *err = DICOP_ERROR_PROT_FORMAT;
        return NULL;
    }

    cur = root->children;
    while (cur) {
        if (xmlStrcmp(cur->name, BAD_CAST "error") == 0) {
            message->error = atoi((char *)xmlNodeGetContent(cur));
            break;
        }
        cur = cur->next;
    }

    cur = root->children;
    while (cur) {
        if (xmlStrcmp(cur->name, BAD_CAST "app") == 0) {
            message->app_name = strdup((char *)xmlNodeGetContent(cur));
            if (message->app_name == NULL) {
                free(message);
                xmlFreeDoc(doc);
                *err = DICOP_ERROR_MEMORY;
                return NULL;
            }
            break;
        }
        cur = cur->next;
    }

    if (message->app_name == NULL) {
        PRINT_ERROR("Unrecognized message format [app]\n");
        free(message);
        xmlFreeDoc(doc);
        *err = DICOP_ERROR_PROT_FORMAT;
        return NULL;
    }

    cur = root->children;
    while (cur) {
        if (xmlStrcmp(cur->name, BAD_CAST "type") == 0) {
            str_type = xmlNodeGetContent(cur);
            if (xmlStrcmp(str_type, BAD_CAST "DefRequest") == 0)
                message->type = DICOP_MESSAGE_TYPE_DEF_REQ;
            else if (xmlStrcmp(str_type, BAD_CAST "DefReply") == 0)
                message->type = DICOP_MESSAGE_TYPE_DEF_REP;
            else if (xmlStrcmp(str_type, BAD_CAST "Context") == 0)
                message->type = DICOP_MESSAGE_TYPE_CONTEXT;
            else
                message->type = DICOP_MESSAGE_TYPE_UNKNOWN;
            break;
        }
        cur = cur->next;
    }

    if (message->type <= DICOP_MESSAGE_TYPE_INITIAL ||
        message->type >= DICOP_MESSAGE_TYPE_UNKNOWN) {
        PRINT_ERROR("Unrecognized message format [type]\n");
        free(message);
        xmlFreeDoc(doc);
        *err = DICOP_ERROR_PROT_FORMAT;
        return NULL;
    }

    switch (message->type) {
        case DICOP_MESSAGE_TYPE_DEF_REQ:
            break;
        case DICOP_MESSAGE_TYPE_DEF_REP:
            if ((*err = _dicop_message_parse_defreply(root->children, message)) < 0) {
                _dicop_message_destroy(message);
                xmlFreeDoc(doc);
                return NULL;
            }
            break;
        case DICOP_MESSAGE_TYPE_CONTEXT:
            if ((*err = _dicop_message_parse_context(root->children, message)) < 0) {
                _dicop_message_destroy(message);
                xmlFreeDoc(doc);
                return NULL;
            }
            break;
    }

    xmlFreeDoc(doc);

    return message;
}



static void
_dicop_message_destroy(struct dicop_message *message)
{
    struct dicop_method_desc *method_desc, *mdesc_pos;
    struct dicop_signal_desc *signal_desc, *sdesc_pos;
    struct dicop_method_data *method_data, *mdata_pos;
    struct dicop_signal_data *signal_data, *sdata_pos;

    if (message) {
        list_for_each_entry_safe(struct dicop_method_desc, method_desc, mdesc_pos,
                                 &message->meth_desc_list, desc_node) {
            list_del(&method_desc->desc_node);
            _dicop_method_desc_destroy(method_desc);
        }

        list_for_each_entry_safe(struct dicop_method_data, method_data, mdata_pos,
                                 &message->meth_data_list, data_node) {
            list_del(&method_data->data_node);
            _dicop_method_data_destroy(method_data);
        }

        list_for_each_entry_safe(struct dicop_signal_desc, signal_desc, sdesc_pos,
                                 &message->sig_desc_list, desc_node) {
            list_del(&signal_desc->desc_node);
            _dicop_signal_desc_destroy(signal_desc);
        }

        list_for_each_entry_safe(struct dicop_signal_data, signal_data, sdata_pos,
                                 &message->sig_data_list, data_node) {
            list_del(&signal_data->data_node);
            _dicop_signal_data_destroy(signal_data);
        }

#ifdef WIN32
        CloseHandle(message->session_sem);
#else
#if 0
        sem_destroy(&message->session_sem);
#else
        sem_close(message->session_sem);
        sem_unlink("session_sem");
#endif
#endif

        if (message->app_name) free(message->app_name);
        free(message);
    }
}

static struct dicop_message *
_dicop_message_get_by_session(queue_t *queue, unsigned int session_id)
{
    struct dicop_message *message;
    int queue_idx;

    if (!queue_is_empty(queue)) {
        for (queue_idx = 1; queue_idx <= queue->count; queue_idx++) {
            message = (struct dicop_message *)queue_peek_nth(queue, queue_idx);
            if (message->session_id == session_id) {
                message = (struct dicop_message *)queue_steal_nth(queue, queue_idx);
                return message;
            }
        }
    }

    return NULL;
}

#ifdef WIN32
DWORD WINAPI
_dicop_message_handler(LPVOID thread_arg)
#else
static void *
_dicop_message_handler(void *thread_arg)
#endif
{
    struct dicop_message *income_msg, *request_msg, *reply_msg;
    struct dicop_app *app;
    struct dicop_method_desc *method_desc;
    struct dicop_method_data *method_data, *method_reply_data;
    struct dicop_signal_desc *signal_desc;
    struct dicop_signal_data *signal_data;
    struct list_head *data_list_pos;
    struct list_head *callback_reply;
    struct list_head *data_safe;
    int has_method_reply = 0;
    int i, error;

    while (!goodbye) {
        error = DICOP_OK;

        PRINT_DEBUG("Waiting message to come ...\n");
        income_msg = queue_pop_wait(message_queue);

        if (income_msg == NULL || income_msg->conn_id <= 0)
            continue;

        PRINT_DEBUG("Got a message (type = %d)\n", income_msg->type);
        switch (income_msg->type) {
            /* If received a DefRequest message, construct DefReply and send it back */
            case DICOP_MESSAGE_TYPE_DEF_REQ:
                app = _dicop_app_lookup(income_msg->app_name);
                if (app == NULL) {
                    /* Create an empty app with error code */
                    app = dicop_app_new(income_msg->app_name);
                    error = DICOP_ERROR_APP;
                }

                /* Update income_msg for sending back */
                reply_msg = _dicop_message_new(income_msg->conn_id,
                                               income_msg->app_name,
                                               DICOP_MESSAGE_TYPE_DEF_REP);
                if (reply_msg == NULL) {
                    PRINT_ERROR("Unable to create message.\n");
                    _dicop_message_destroy(income_msg);
                    break;
                }

                /* Save app pointer to get method/signal description while sending message */
                reply_msg->app = app;
                reply_msg->session_id = income_msg->session_id;
                reply_msg->error = error;

                _dicop_message_send(reply_msg);

                /* Save client ID to appropriate position of client_id[] in app */
                for (i = 0; i < DICOP_MAX_CONN && reply_msg->error == DICOP_OK; i++) {
                    if (app->client_id[i] == 0) {
                        app->client_id[i] = income_msg->conn_id;
                        ++app->client_count;
                        break;
                    }
                }

                if (reply_msg->error < 0)
                    dicop_app_destroy(app);

                _dicop_message_destroy(reply_msg);
                _dicop_message_destroy(income_msg);

                break;
            /*
             * If received a DefReply message, push it to session queue
             * for dicop_app_query() to pop.
             */
            case DICOP_MESSAGE_TYPE_DEF_REP:
                /* Check if there's someone waiting for this session */
                request_msg = _dicop_message_get_by_session(request_queue,
                                                            income_msg->session_id);
                if (request_msg) {
                    PRINT_DEBUG("Ack for DefReply with session=%u\n",
                                income_msg->session_id);
                    /*
                     * Push message to session queue for dicop_app_query()
                     * and post semaphore.
                     */
                    queue_push(reply_queue, (void *)income_msg);
#ifdef WIN32
                    ReleaseSemaphore(request_msg->session_sem, 1, NULL);
#else
                    PRINT_DEBUG("Post semaphore for DICOP_MESSAGE_TYPE_DEF_REP\n");
                    sem_post(request_msg->session_sem);
#endif
                }
                else {
                    PRINT_DEBUG("Drop message with session=%u\n", income_msg->session_id);
                    /* No request for this session, feel free to drop this message */
                    _dicop_message_destroy(income_msg);
                }

                break;
            case DICOP_MESSAGE_TYPE_CONTEXT: /* Message for both */
                app = _dicop_app_lookup(income_msg->app_name);
                if (app == NULL) {
                    /* Create an empty app with error code */
                    app = dicop_app_new(income_msg->app_name);
                    error = DICOP_ERROR_APP;
                }

                /* If message is a method call, run method callback */
                list_for_each_safe(data_list_pos, data_safe, &income_msg->meth_data_list) {
                    method_data = list_entry(data_list_pos,
                                             struct dicop_method_data,
                                             data_node);
                    if (method_data) {
                        if (method_data->type == DICOP_METHOD_TYPE_CALL) {
                            method_desc = _dicop_method_desc_lookup(
                                                          app,
                                                          method_data->name);

                            if (method_desc && method_desc->callback) {
                                callback_reply =
                                    method_desc->callback(&method_data->arg_list);

                                if (method_desc->rep_nbr > 0) {
                                    /* TODO:
                                     *    Check if callback_reply number is equal to rep_nbr.
                                     */
                                    if (callback_reply == NULL) {
                                        callback_reply = dicop_callback_null_reply();
                                        error = DICOP_ERROR_METHOD_REPLY;
                                    }

                                    reply_msg = _dicop_message_new(
                                                    income_msg->conn_id,
                                                    income_msg->app_name,
                                                    DICOP_MESSAGE_TYPE_CONTEXT);
                                    if (reply_msg == NULL) {
                                        PRINT_ERROR("Unable to create message.\n");
                                        break;
                                    }

                                    reply_msg->session_id = income_msg->session_id;
                                    reply_msg->error = error;

                                    if (error == DICOP_OK) {
                                        method_reply_data =
                                            calloc(1, sizeof(struct dicop_method_data));
                                        if (method_reply_data == NULL) {
                                            perror("calloc");
                                            break;
                                        }

                                        INIT_LIST_HEAD(&method_reply_data->data_node);
                                        INIT_LIST_HEAD(&method_reply_data->arg_list);
                                        /* FIXME */
                                        method_reply_data->name = strdup(method_data->name);
                                        method_reply_data->type = DICOP_METHOD_TYPE_REPLY;

                                        list_splice_init(callback_reply,
                                                         &method_reply_data->arg_list);

                                        if (callback_reply)
                                            free(callback_reply);

                                        list_add_tail(&method_reply_data->data_node,
                                                      &reply_msg->meth_data_list);
                                    }

                                    _dicop_message_send(reply_msg);
                                    _dicop_message_destroy(reply_msg);
                                }
                            }
                        }
                        else if (method_data->type == DICOP_METHOD_TYPE_REPLY) {
                            has_method_reply = 1;
                        }
                    }
                }

                /* If message is a signal, run signal callback */
                list_for_each_safe(data_list_pos, data_safe, &income_msg->sig_data_list) {
                    signal_data = list_entry(data_list_pos,
                                             struct dicop_signal_data,
                                             data_node);
                    if (signal_data) {
                        signal_desc = _dicop_signal_desc_lookup(app, signal_data->name);
                        if (signal_desc) {
                            signal_desc->callback(&signal_data->arg_list);
                        }
                    }
                }

                /*
                 * If message is a method reply:
                 *   1) push message to session queue for dicop_method_call_wait()
                 *   2) post session semaphore to notify waiter to pop message
                 *      from session queue.
                 */
                if (has_method_reply) {
                    has_method_reply = 0;
                    request_msg = _dicop_message_get_by_session(
                                      request_queue,
                                      income_msg->session_id);
                    if (request_msg) {
                        queue_push(reply_queue, (void *)income_msg);
#ifdef WIN32
                        ReleaseSemaphore(request_msg->session_sem, 1, NULL);
#else
                        sem_post(request_msg->session_sem);
#endif
                        break; /* DON'T destroy income message */
                    }
                }

                _dicop_message_destroy(income_msg);

                break;
        }
    }

    return NULL;
}

#ifdef WIN32
DWORD WINAPI
_dicop_message_recv(LPVOID thread_arg)
#else
static void *
_dicop_message_recv(void *thread_arg)
#endif
{
    int sockfd = *((int *)&thread_arg);
    struct dicop_message *message;
    struct dicop_app *app_node, *app_pos;
    struct OutRingBuffer *ring_buffer;
    char *sock_buffer;
    char *xml_buffer;
    char *begin_tag = NULL;
    char *end_tag = NULL;
    char *sockbuf_ptr;
    int ring_buf_sz = DICOP_BUF_SZ * 2;
    int sock_buf_sz = DICOP_BUF_SZ/4;
    int nread = 0;
    int xml_len = 0;
    int err;
    int cli_idx;


#ifdef DEBUG_SOCKET
    PRINT_DEBUG("sockfd = %d\n", sockfd);
#endif

    if (!rb_init(&ring_buffer, ring_buf_sz))
        return NULL;

    sock_buffer = calloc(1, sock_buf_sz);
    if (sock_buffer == NULL) {
        perror("calloc");
        return NULL;
    }

    while (!goodbye) {
        bzero(sock_buffer, sock_buf_sz);

        if ((nread = recv(sockfd, sock_buffer, sock_buf_sz, 0)) < 0) {
            perror("recv");
            break;
        }

        if (nread == 0) {
            /* Client has shutdown */
            break;
        }

#ifdef DEBUG_SOCKET
        PRINT_DEBUG("Recv length: %d\n", nread);
        //PRINT_DEBUG("Received raw data:\n%s\n\n", sock_buffer);
#endif

        sockbuf_ptr = sock_buffer;

        while (sockbuf_ptr < sock_buffer + nread) {
            /* Check for begin tag "<?xml" */
            if (begin_tag == NULL) {
                begin_tag = strstr(sockbuf_ptr, "<?xml");
                if (begin_tag) {
                    end_tag = strstr(sockbuf_ptr, "</dicop>");
                    if (end_tag) {
                        end_tag += strlen("</dicop>");
                        xml_len = end_tag - begin_tag;

                        xml_buffer = calloc(1, xml_len);
                        memcpy(xml_buffer, begin_tag, xml_len);
                        sockbuf_ptr = end_tag + 1;
                    }
                    else {
                        xml_len = nread - (begin_tag - sock_buffer);
                        rb_write(ring_buffer, begin_tag, xml_len);
                        break;  /* Read more data from sock */
                    }
                }
                else {
                    break;   /* Begin tag not found in buffer */
                }
            }

            /* Check for end tag "</dicop>" */
            if (end_tag == NULL) {
                end_tag = strstr(sockbuf_ptr, "</dicop>");
                if (end_tag) {
                    end_tag += strlen("</dicop>");
                    xml_len += end_tag - sockbuf_ptr;
                    sockbuf_ptr = end_tag + 1;

                    if (xml_len > ring_buf_sz) {
                        rb_clear(ring_buffer);
                        begin_tag = end_tag = NULL;
                        PRINT_WARNING("*** XML content size exceeded ring buffer ***\n");
                        break;
                    }

                    rb_write(ring_buffer, sock_buffer, end_tag - sock_buffer);

                    if (xml_len != rb_data_size(ring_buffer))
                        PRINT_WARNING("*** XML length mismatch! ***\n");

                    xml_buffer = calloc(1, xml_len);
                    rb_read(ring_buffer, xml_buffer, xml_len);
                }
                else {
                    xml_len += nread;
                    rb_write(ring_buffer, sock_buffer, nread);
                    break;  /* Read more data from sock */
                }
            }

#ifdef DEBUG_XML
            PRINT_DEBUG("\nReceived Message:\n%s\n\n", xml_buffer);
#endif

            if ((message = _dicop_message_parse(xml_buffer, xml_len, &err)) != NULL) {
                message->conn_id = sockfd;
                queue_push(message_queue, (void *)message);
            }
            else
                dicop_error_print(err);

            free(xml_buffer);
            xml_len = 0;
            begin_tag = end_tag = NULL;
        }
    }

#ifdef DEBUG_SOCKET
    PRINT_DEBUG("Close sock %d\n", sockfd);
#endif

#ifdef WIN32
    closesocket(sockfd);
#else
    close(sockfd);
#endif

    rb_free(ring_buffer);
    free(sock_buffer);

    if (serv_count > 0) {
        /* Traverse app list to find out and update apps with this client ID */
        list_for_each_entry_safe(struct dicop_app, app_node, app_pos, &app_list, app_node) {
            for (cli_idx = 0; cli_idx < DICOP_MAX_CONN; cli_idx++) {
                if (app_node->client_id[cli_idx] == sockfd) {
                    app_node->client_id[cli_idx] = 0;
                    --app_node->client_count;
                    break;
                }
            }

            if (app_node->client_count < 0)
                PRINT_ERROR("Error while updating app client count.\n");
        }

        --serv_count;
    }

    return NULL;
}

static int
_dicop_message_check(struct dicop_message *message)
{
    if (message) {
        if (!message->app_name)
            return DICOP_ERROR_APP;

        switch (message->type) {
            case DICOP_MESSAGE_TYPE_DEF_REQ:
                break;
            case DICOP_MESSAGE_TYPE_DEF_REP:
                break;
            case DICOP_MESSAGE_TYPE_CONTEXT:
                /* A CONTEXT message must contain either method data or signal data */
                if (list_empty(&message->meth_data_list) &&
                    list_empty(&message->sig_data_list))
                    return DICOP_ERROR_PROTOCOL;

                /* If this is a method call message, the conn_id must be > 0 */
                if (!list_empty(&message->meth_data_list))
                    if (message->conn_id <= 0)
                        return DICOP_ERROR_CONNECTION;

                break;
            default:
                return DICOP_ERROR_TYPE;
        }

        return DICOP_OK;
    }

    return DICOP_ERROR_ARGUMENTS;
}

static int
_dicop_message_send(struct dicop_message *message)
{
    xmlDocPtr doc;
    xmlNodePtr root_node, meth_node, sig_node, arg_node;
    xmlChar session_id[11];
    xmlChar error[5];
    struct dicop_method_desc *method_desc;
    struct dicop_method_data *method_data;
    struct dicop_signal_desc *signal_desc;
    struct dicop_signal_data *signal_data;
    struct dicop_args *args, *reps;
    struct list_head *list_pos, *arg_list_pos, *rep_list_pos;
    struct list_head *desc_safe, *data_safe, *arg_safe, *rep_safe;
    xmlChar *xmlbuff;
    int buffersize;
    int sent_char_count = 0;

    if (_dicop_message_check(message) != DICOP_OK)
        return DICOP_ERROR_ARGUMENTS;

    doc = xmlNewDoc(BAD_CAST "1.0");
    root_node = xmlNewNode(NULL, BAD_CAST "dicop");
    xmlDocSetRootElement(doc, root_node);

    snprintf((char *)session_id, 11, "%u", message->session_id);
    xmlNewChild(root_node, NULL, BAD_CAST "session", session_id);
    snprintf((char *)error, 5, "%d", message->error);
    xmlNewChild(root_node, NULL, BAD_CAST "error", error);

    xmlNewChild(root_node, NULL, BAD_CAST "app", BAD_CAST message->app_name);

    switch (message->type) {
        case DICOP_MESSAGE_TYPE_DEF_REQ:
            xmlNewChild(root_node, NULL, BAD_CAST "type", BAD_CAST "DefRequest");
            break;
        case DICOP_MESSAGE_TYPE_DEF_REP:
            xmlNewChild(root_node, NULL, BAD_CAST "type", BAD_CAST "DefReply");
            break;
        case DICOP_MESSAGE_TYPE_CONTEXT:
            xmlNewChild(root_node, NULL, BAD_CAST "type", BAD_CAST "Context");
            break;
    }

    if (message->type == DICOP_MESSAGE_TYPE_DEF_REP) {
        /* Obtain method/signal description from app */
        if (message->app == NULL) {
            PRINT_ERROR("Unable to get method/signal descriptions [app=NULL]\n");
            goto exception;
        }

        list_for_each_safe(list_pos, desc_safe, &message->app->meth_desc_list) {
            method_desc = list_entry(list_pos, struct dicop_method_desc, desc_node);
            if (method_desc) {
                /* Create "method call" description context */
                meth_node = xmlNewChild(root_node, NULL, BAD_CAST "method", NULL);
                xmlNewProp(meth_node, BAD_CAST "name", BAD_CAST method_desc->name);
                xmlNewProp(meth_node, BAD_CAST "type", BAD_CAST "call");

                if (!list_empty(&method_desc->arg_list)) {
                    list_for_each_safe(arg_list_pos, arg_safe, &method_desc->arg_list) {
                        args = list_entry(arg_list_pos, struct dicop_args, arg_node);
                        if (args) {
                            arg_node = xmlNewChild(meth_node, NULL, BAD_CAST "arg", NULL);
                            xmlNewProp(arg_node, BAD_CAST "name", BAD_CAST args->name);
                            //xmlNodeSetContent(arg_node, BAD_CAST args->value);
                        }
                    }
                }

                /* Create "method reply" description context */
                meth_node = xmlNewChild(root_node, NULL, BAD_CAST "method", NULL);
                xmlNewProp(meth_node, BAD_CAST "name", BAD_CAST method_desc->name);
                xmlNewProp(meth_node, BAD_CAST "type", BAD_CAST "reply");

                if (!list_empty(&method_desc->rep_list)) {
                    list_for_each_safe(rep_list_pos, rep_safe, &method_desc->rep_list) {
                        reps = list_entry(rep_list_pos, struct dicop_args, arg_node);
                        if (reps) {
                            arg_node = xmlNewChild(meth_node, NULL, BAD_CAST "arg", NULL);
                            xmlNewProp(arg_node, BAD_CAST "name", BAD_CAST reps->name);
                            //xmlNodeSetContent(arg_node, BAD_CAST reps->value);
                        }
                    }
                }
            }
        }

        list_for_each_safe(list_pos, desc_safe, &message->app->sig_desc_list) {
            signal_desc = list_entry(list_pos, struct dicop_signal_desc, desc_node);
            if (signal_desc) {
                sig_node = xmlNewChild(root_node, NULL, BAD_CAST "signal", NULL);
                xmlNewProp(sig_node, BAD_CAST "name", BAD_CAST signal_desc->name);

                list_for_each_safe(arg_list_pos, arg_safe, &signal_desc->arg_list) {
                    args = list_entry(arg_list_pos, struct dicop_args, arg_node);
                    if (args) {
                        arg_node = xmlNewChild(sig_node, NULL, BAD_CAST "arg", NULL);
                        xmlNewProp(arg_node, BAD_CAST "name", BAD_CAST args->name);
                        //xmlNodeSetContent(arg_node, BAD_CAST args->value);
                    }
                }
            }
        }
    }
    else if (message->type == DICOP_MESSAGE_TYPE_CONTEXT) {
        list_for_each_safe(list_pos, data_safe, &message->meth_data_list) {
            method_data = list_entry(list_pos, struct dicop_method_data, data_node);
            if (method_data) {
                meth_node = xmlNewChild(root_node, NULL, BAD_CAST "method", NULL);
                xmlNewProp(meth_node, BAD_CAST "name", BAD_CAST method_data->name);
                if (method_data->type == DICOP_METHOD_TYPE_CALL)
                    xmlNewProp(meth_node, BAD_CAST "type", BAD_CAST "call");
                else if (method_data->type == DICOP_METHOD_TYPE_REPLY)
                    xmlNewProp(meth_node, BAD_CAST "type", BAD_CAST "reply");

                list_for_each_safe(arg_list_pos, arg_safe, &method_data->arg_list) {
                    args = list_entry(arg_list_pos, struct dicop_args, arg_node);
                    if (args) {
                        arg_node = xmlNewChild(meth_node, NULL, BAD_CAST "arg", NULL);
                        if (!args->name || !args->value) {
                            PRINT_ERROR("Null method argument\n");
                            break;
                        }
                        xmlNewProp(arg_node, BAD_CAST "name", BAD_CAST args->name);
                        xmlNodeSetContent(arg_node, BAD_CAST args->value);
                    }
                }
            }
        }

        list_for_each_safe(list_pos, data_safe, &message->sig_data_list) {
            signal_data = list_entry(list_pos, struct dicop_signal_data, data_node);
            if (signal_data) {
                sig_node = xmlNewChild(root_node, NULL, BAD_CAST "signal", NULL);
                xmlNewProp(sig_node, BAD_CAST "name", BAD_CAST signal_data->name);

                list_for_each_safe(arg_list_pos, arg_safe, &signal_data->arg_list) {
                    args = list_entry(arg_list_pos, struct dicop_args, arg_node);
                    if (args) {
                        arg_node = xmlNewChild(sig_node, NULL, BAD_CAST "arg", NULL);
                        if (!args->name || !args->value) {
                            PRINT_ERROR("Null signal argument\n");
                            break;
                        }
                        xmlNewProp(arg_node, BAD_CAST "name", BAD_CAST args->name);
                        xmlNodeSetContent(arg_node, BAD_CAST args->value);
                    }
                }
            }
        }
    }

    /* Dump doc to buffer */
    xmlDocDumpFormatMemory(doc, &xmlbuff, &buffersize, 1);

#ifdef DEBUG_XML
    PRINT_DEBUG("Sending message:\n%s\n", xmlbuff);
#endif

    if ((sent_char_count = send(message->conn_id, xmlbuff, buffersize, 0)) < 0)
        perror("send");

    xmlFree(xmlbuff);

exception:
    xmlFreeDoc(doc);

    return sent_char_count;
}

void
dicop_error_print(int err)
{
    switch (err) {
        case DICOP_OK:
            printf("Success\n"); break;
        case DICOP_ERROR_SYSTEM:
            printf("Bad system call\n"); break;
        case DICOP_ERROR_ARGUMENTS:
            printf("Bad arguments\n"); break;
        case DICOP_ERROR_PROTOCOL:
            printf("Bad protocol\n"); break;
        case DICOP_ERROR_PROT_FORMAT:
            printf("Bad protocol data format\n"); break;
        case DICOP_ERROR_PROT_ROOT:
            printf("Bad protocol data root\n"); break;
        case DICOP_ERROR_MEMORY:
            printf("Error while allocating memory\n"); break;
        case DICOP_ERROR_CONNECTION:
            printf("Bad connection\n"); break;
        case DICOP_ERROR_APP:
            printf("Unknown application name\n"); break;
        case DICOP_ERROR_METHOD_CALL:
            printf("Bad method call\n"); break;
        case DICOP_ERROR_METHOD_REPLY:
            printf("Bad method reply\n"); break;
        case DICOP_ERROR_SIGNAL:
            printf("Bad signal\n"); break;
        case DICOP_ERROR_TYPE:
            printf("Bad protocol type\n"); break;
        default:
            printf("Unknown error\n"); break;
    }
}

#ifdef WIN32
DWORD WINAPI
_dicop_server_connection_accept(LPVOID thread_arg)
#else
void *
_dicop_server_connection_accept(void *thread_arg)
#endif
{
    int clientfd;
#ifdef WIN32
    HANDLE msg_recv_thread;
    DWORD thread_id;
#else
    pthread_t msg_recv_thread;
#endif
    struct sockaddr_in client_addr;
    int addrlen = sizeof(client_addr);

    while (!goodbye) {
        if (serv_count <= DICOP_MAX_CONN) {
            PRINT_INFO("Waiting for connection\n");
            /* Wait and Accept connection */
#ifdef WIN32
            clientfd = accept(servfd, (struct sockaddr *)&client_addr,
                              (int *)&addrlen);
#else
            clientfd = accept(servfd, (struct sockaddr *)&client_addr,
                              (socklen_t *)&addrlen);
#endif
            PRINT_INFO("New client accepted: %d\n", clientfd);

#ifdef WIN32
            msg_recv_thread = CreateThread(NULL, 0, _dicop_message_recv,
                                          (LPVOID)clientfd, 0, &thread_id);
            CloseHandle(msg_recv_thread);
#else
            pthread_create(&msg_recv_thread, NULL, &_dicop_message_recv,
                           (void *)clientfd);
            pthread_detach(msg_recv_thread);
#endif
            ++serv_count;
        }
        else
            sleep(1);
    }

    return NULL;
}

/* TODO: Daemonizing this function (should be standalone daemon in the future) */
int
dicop_server_create(int port)
{
    struct sockaddr_in dest;

    int on = 1;

    if (port == 0)
        port = DICOP_PORT;

    serv_count = 0;

    servfd = socket(PF_INET, SOCK_STREAM, 0);

    if (setsockopt(servfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) {
        perror("setsockopt() error");
        return DICOP_ERROR_SYSTEM;
    }

    if (setsockopt(servfd, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)) < 0) {
        perror("setsockopt() error");
        return DICOP_ERROR_SYSTEM;
    }

    if (setsockopt(servfd, SOL_TCP, TCP_NODELAY, &on, sizeof(on)) < 0) {
        perror("setsockopt() error");
        return DICOP_ERROR_SYSTEM;
    }

    bzero(&dest, sizeof(dest));
    dest.sin_family = AF_INET;
    dest.sin_port = htons(port);
    dest.sin_addr.s_addr = INADDR_ANY;

    /* Assign a port number to socket */
    bind(servfd, (struct sockaddr *)&dest, sizeof(dest));

    /* make it listen to socket with max DICOP_MAX_CONN connections */
    listen(servfd, DICOP_MAX_CONN);

#ifdef WIN32
    serv_accept_thread = CreateThread(NULL, 0, _dicop_server_connection_accept,
                                      (LPVOID)0, 0, &serv_accept_thread_id);
    serv_msg_proc_thread = CreateThread(NULL, 0, _dicop_message_handler,
                                        (LPVOID)0, 0, &serv_msg_proc_thread_id);
#else
    pthread_create(&serv_accept_thread, NULL, &_dicop_server_connection_accept, NULL);
    pthread_create(&serv_msg_proc_thread, NULL, &_dicop_message_handler, NULL);
#endif

    return DICOP_OK;
}

int
dicop_server_terminate(void)
{
    // FIXME: Clear connection gracefully
#ifdef WIN32
    TerminateThread(serv_accept_thread, 0);
    TerminateThread(serv_msg_proc_thread, 0);
    closesocket(servfd);
#else
    pthread_cancel(serv_accept_thread);
    pthread_cancel(serv_msg_proc_thread);
    close(servfd);
#endif
    queue_destroy(message_queue);
    return DICOP_OK;
}

int
dicop_client_connect(char *serv_ip, int serv_port)
{
    int sockfd;
    struct sockaddr_in dest;
    int on = 1;

    if (serv_ip == NULL || serv_port <= 0)
        return DICOP_ERROR_ARGUMENTS;

    /* create socket */
    sockfd = socket(PF_INET, SOCK_STREAM, 0);

    if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) {
        perror("setsockopt() error");
        return DICOP_ERROR_SYSTEM;
    }

    if (setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)) < 0) {
        perror("setsockopt() error");
        return DICOP_ERROR_SYSTEM;
    }

    if (setsockopt(sockfd, SOL_TCP, TCP_NODELAY, &on, sizeof(on)) < 0) {
        perror("setsockopt() error");
        return DICOP_ERROR_SYSTEM;
    }

    /* initialize value in dest */
    bzero(&dest, sizeof(dest));

    dest.sin_family = AF_INET;
    dest.sin_port = htons(serv_port);
#ifdef WIN32
    dest.sin_addr.s_addr = inet_addr(serv_ip);
#else
    inet_aton(serv_ip, &dest.sin_addr);
#endif

    /* Connecting to server */
    if (connect(sockfd, (struct sockaddr *)&dest, sizeof(dest)) < 0) {
        perror("Connect");
        return DICOP_ERROR_CONNECTION;
    }

#ifdef WIN32
    cli_msg_recv_thread = CreateThread(NULL, 0, _dicop_message_recv, (LPVOID)sockfd,
                                       0, &cli_msg_recv_thread_id);
    CloseHandle(cli_msg_recv_thread);
    cli_msg_proc_thread = CreateThread(NULL, 0, _dicop_message_handler, (LPVOID)0,
                                       0, &cli_msg_proc_thread_id);
#else
    pthread_create(&cli_msg_recv_thread, NULL, &_dicop_message_recv, (void *)sockfd);
    pthread_detach(cli_msg_recv_thread);
    pthread_create(&cli_msg_proc_thread, NULL, &_dicop_message_handler, NULL);
#endif

    return sockfd;
}

void
dicop_client_disconnect(int sockfd)
{
#ifdef WIN32
    TerminateThread(cli_msg_recv_thread, 0);
    TerminateThread(cli_msg_proc_thread, 0);
    /* Close connection */
    closesocket(sockfd);
#else
    pthread_cancel(cli_msg_recv_thread);
    pthread_cancel(cli_msg_proc_thread);
    /* Close connection */
    close(sockfd);
#endif
}

void
dicop_callback_get_args(void *args, int arg_nbr, ...)
{
    struct list_head *arglist = (struct list_head *)args;
    struct dicop_args *arg, *pos;
    va_list ap;
    char *name;
    char *value;
    int i;

    if (!args || arg_nbr <= 0 || arg_nbr > DICOP_MAX_ARG_NBR)
        return;

    va_start(ap, arg_nbr);

    for (i = 0; i < arg_nbr; i++) {
        name = va_arg(ap, char *);
        value = va_arg(ap, char *);
        list_for_each_entry_safe(struct dicop_args, arg, pos, arglist, arg_node) {
            if (strncmp(arg->name, name, strlen(arg->name)) == 0) {
                strncpy(value, arg->value, strlen(arg->value));
                PRINT_DEBUG("Setting [%s = %s]\n", name, value);
                break;
            }
        }
    }

    va_end(ap);
}

struct list_head *dicop_callback_put_reply(int rep_nbr, ...)
{
    struct list_head *replist;
    struct dicop_args *reparg[DICOP_MAX_ARG_NBR];
    va_list ap;
    char *rep_name;
    char *rep_value;
    int i;

    if (rep_nbr <= 0 || rep_nbr > DICOP_MAX_ARG_NBR)
        return NULL;

    replist = malloc(sizeof(struct list_head));
    if (replist == NULL) {
        perror("malloc");
        return NULL;
    }

    INIT_LIST_HEAD(replist);

    va_start(ap, rep_nbr);

    for (i = 0; i < rep_nbr; i++) {
        reparg[i] = malloc(sizeof(struct dicop_args));
        if (reparg[i] == NULL) {
            perror("malloc");
            return NULL;
        }

        rep_name = va_arg(ap, char *);
        reparg[i]->name = strdup(rep_name);
        rep_value = va_arg(ap, char *);
        reparg[i]->value = strdup(rep_value);
        list_add_tail(&reparg[i]->arg_node, replist);
    }

    va_end(ap);

    return replist;
}

/* TODO: Put error msg in this */
struct list_head *
dicop_callback_null_reply(void)
{
    struct list_head *replist;

    replist = malloc(sizeof(struct list_head));
    if (!replist) {
        perror("malloc");
        return NULL;
    }

    INIT_LIST_HEAD(replist);

    return replist;
}
